Цель проекта:
Провести оценку результатов A/B-теста.
Задачи проекта:
Оцените корректность проведения теста.
Проанализируйте результаты теста.
Чтобы оценить корректность проведения теста, проверьте:
пересечение тестовой аудитории с конкурирующим тестом;
совпадение теста и маркетинговых событий, другие проблемы временных границ теста.
Ожидаемый эффект: за 14 дней с момента регистрации в системе пользователи покажут улучшение каждой метрики не менее, чем на 10%:
Загрузите данные теста, проверьте корректность его проведения и проанализируйте полученные результаты.
Выгрузить все датасеты. Изучить общую информацию о датасетах, какого типа данные представлены и в каких количествах.
Проверить соответствие данных требованиям технического задания. Проверьте корректность всех пунктов технического задания.
Проанализировать время проведения теста. Убедитесь, что оно не совпадает с маркетинговыми и другими активностями.
Изучить аудиторию теста. Удостоверьтесь, что нет пересечений с конкурирующим тестом и нет пользователей, участвующих в двух группах теста одновременно. Проверьте равномерность распределения пользователей по тестовым группам и правильность их формирования.
import pandas as pd
from scipy import stats as st
import datetime as dt
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
from math import sqrt
import plotly.express as px
from plotly import graph_objects as go
import math as mth
from statsmodels.stats.proportion import proportions_ztest
marketing_events = pd.read_csv('ab_project_marketing_events.csv', parse_dates = ['start_dt','finish_dt'])
users = pd.read_csv('final_ab_new_users.csv', parse_dates = ['first_date'])
events = pd.read_csv('final_ab_events.csv', parse_dates = ['event_dt'])
participants = pd.read_csv('final_ab_participants.csv')
marketing_events.info()
marketing_events.head()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 14 entries, 0 to 13 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 name 14 non-null object 1 regions 14 non-null object 2 start_dt 14 non-null datetime64[ns] 3 finish_dt 14 non-null datetime64[ns] dtypes: datetime64[ns](2), object(2) memory usage: 576.0+ bytes
| name | regions | start_dt | finish_dt | |
|---|---|---|---|---|
| 0 | Christmas&New Year Promo | EU, N.America | 2020-12-25 | 2021-01-03 |
| 1 | St. Valentine's Day Giveaway | EU, CIS, APAC, N.America | 2020-02-14 | 2020-02-16 |
| 2 | St. Patric's Day Promo | EU, N.America | 2020-03-17 | 2020-03-19 |
| 3 | Easter Promo | EU, CIS, APAC, N.America | 2020-04-12 | 2020-04-19 |
| 4 | 4th of July Promo | N.America | 2020-07-04 | 2020-07-11 |
Перед нами датасет marketing_events, состоящий из 4 столбцов и 14 строк. Пропусков не обнаружено. Данные содержат информацию о маркетинговых событиях на 2020 год.
users.info()
users.head()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 61733 entries, 0 to 61732 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 user_id 61733 non-null object 1 first_date 61733 non-null datetime64[ns] 2 region 61733 non-null object 3 device 61733 non-null object dtypes: datetime64[ns](1), object(3) memory usage: 1.9+ MB
| user_id | first_date | region | device | |
|---|---|---|---|---|
| 0 | D72A72121175D8BE | 2020-12-07 | EU | PC |
| 1 | F1C668619DFE6E65 | 2020-12-07 | N.America | Android |
| 2 | 2E1BF1D4C37EA01F | 2020-12-07 | EU | PC |
| 3 | 50734A22C0C63768 | 2020-12-07 | EU | iPhone |
| 4 | E1BDDCE0DAFA2679 | 2020-12-07 | N.America | iPhone |
Перед нами датасет users, состоящий из 4 столбцов и 61733 строк. Пропусков не обнаружено. Данные содержат информацию о всех пользователях, зарегистрировавшихся в интернет-магазине в период с 7 по 21 декабря 2020 года.
events.info()
events.head()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 440317 entries, 0 to 440316 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 user_id 440317 non-null object 1 event_dt 440317 non-null datetime64[ns] 2 event_name 440317 non-null object 3 details 62740 non-null float64 dtypes: datetime64[ns](1), float64(1), object(2) memory usage: 13.4+ MB
| user_id | event_dt | event_name | details | |
|---|---|---|---|---|
| 0 | E1BDDCE0DAFA2679 | 2020-12-07 20:22:03 | purchase | 99.99 |
| 1 | 7B6452F081F49504 | 2020-12-07 09:22:53 | purchase | 9.99 |
| 2 | 9CD9F34546DF254C | 2020-12-07 12:59:29 | purchase | 4.99 |
| 3 | 96F27A054B191457 | 2020-12-07 04:02:40 | purchase | 4.99 |
| 4 | 1FD7660FDF94CA1F | 2020-12-07 10:15:09 | purchase | 4.99 |
Перед нами датасет events, состоящий из 4 столбцов и 440317 строк. В стобце details есть пропуски. Данные содержат информацию о всех событиях новых пользователей в период с 7 декабря 2020 по 4 января 2021 года.
participants.info()
participants.head()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 18268 entries, 0 to 18267 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 user_id 18268 non-null object 1 group 18268 non-null object 2 ab_test 18268 non-null object dtypes: object(3) memory usage: 428.3+ KB
| user_id | group | ab_test | |
|---|---|---|---|
| 0 | D1ABA3E2887B6A73 | A | recommender_system_test |
| 1 | A7A3664BD6242119 | A | recommender_system_test |
| 2 | DABC14FDDFADD29E | A | recommender_system_test |
| 3 | 04988C5DF189632E | A | recommender_system_test |
| 4 | 482F14783456D21B | B | recommender_system_test |
Перед нами датасет participants, состоящий из 3 столбцов и 18268 строк. Пропусков не обнаружено. Данные содержат информацию о участниках тестов.
Мы выгрузили 4 датасета, преобразование типов данных не требуется, названия колонок имеют стандартную форму. Удалим явные дубликаты и рассмотрим колонку details в events. Поскольку эта колонка содержит дополнительные данные о событии, то оставляем пропуски, так как не каждое событие содержит дополнительные данные.
marketing_events = marketing_events.drop_duplicates()
users = users.drop_duplicates()
events = events.drop_duplicates()
participants = participants.drop_duplicates()
Техническое задание
recommender_system_test;product_page,product_cart,purchase.#оставляем лишь участников теста и фильтруем сроки их регистрации
users = users[users['first_date'] <= '2020-12-21']
participants_rec = participants.query('ab_test == "recommender_system_test"')
participants_rec_upd = participants_rec['user_id']
users_rec = users.query('user_id in @participants_rec_upd')
users_upd = users.query('user_id in @participants_rec')
users_upd = users[users['first_date'] <= '2020-12-21']
users_min = users['first_date'].min()
users_max = users['first_date'].max()
display(f'Дата первой регистрации: {users_min}, Дата последней регистрации: {users_max}')
'Дата первой регистрации: 2020-12-07 00:00:00, Дата последней регистрации: 2020-12-21 00:00:00'
#оставляем лишь участников теста
events = events[events['event_dt'] <= '2021-01-04']
events_min = events['event_dt'].min()
events_max = events['event_dt'].max()
display(f'Дата первого события: {events_min}, Дата последнего события: {events_max}')
'Дата первого события: 2020-12-07 00:00:33, Дата последнего события: 2020-12-30 23:36:33'
#проверка на 15% новых пользователей из EU
users_upd_grpb = users_rec.groupby('region')['user_id'].count().reset_index()
display(users_upd_grpb)
regions_grp = users_upd.groupby('region')['user_id'].count().reset_index()
display('Процент новых пользователей из EU:',users_upd_grpb.query('region == "EU"')['user_id'] / regions_grp.query('region == "EU"')['user_id'] * 100)
| region | user_id | |
|---|---|---|
| 0 | APAC | 72 |
| 1 | CIS | 55 |
| 2 | EU | 6351 |
| 3 | N.America | 223 |
'Процент новых пользователей из EU:'
2 15.0 Name: user_id, dtype: float64
Последним пунктом ТЗ мы имеем несостыковку. Планируемая дата остановки - 2021-01-04, в то время как в реальности последнее событие датируется 2020-12-30. Из-за этого мы не получаем ождиаемого времени со дня регистрации - 14 дней. Для пользователей, зарегестрировавшихся в последний день по ТЗ 2020-12-21 имеем лайфтайм 9 дней. Отфильтруем события, совершенные не позже 14 дней.
reg = users_rec[['user_id','first_date']]
events_upd = events.merge(reg, on = 'user_id')
events_upd['days'] = (events_upd['event_dt'] - events_upd['first_date']).dt.days
events_upd.query('days > 14')
| user_id | event_dt | event_name | details | first_date | days | |
|---|---|---|---|---|---|---|
| 28 | 2B06EB547B7AAD08 | 2020-12-22 17:27:15 | purchase | 4.99 | 2020-12-07 | 15 |
| 31 | 2B06EB547B7AAD08 | 2020-12-22 17:27:17 | product_cart | NaN | 2020-12-07 | 15 |
| 34 | 2B06EB547B7AAD08 | 2020-12-22 17:27:15 | product_page | NaN | 2020-12-07 | 15 |
| 37 | 2B06EB547B7AAD08 | 2020-12-22 17:27:15 | login | NaN | 2020-12-07 | 15 |
| 55 | 538F954F6B3AECE4 | 2020-12-27 07:09:20 | purchase | 4.99 | 2020-12-07 | 20 |
| ... | ... | ... | ... | ... | ... | ... |
| 23169 | 016F758EB5C5A5DA | 2020-12-29 07:51:00 | login | NaN | 2020-12-13 | 16 |
| 23211 | E2F981AE3D6A2CE8 | 2020-12-29 15:02:21 | login | NaN | 2020-12-14 | 15 |
| 23232 | 9E537FA089BAECFE | 2020-12-29 14:52:33 | login | NaN | 2020-12-14 | 15 |
| 23372 | D0D1426BAFDD1FE5 | 2020-12-29 09:53:34 | login | NaN | 2020-12-14 | 15 |
| 23405 | 9745CE1D5B111CB1 | 2020-12-29 19:10:57 | login | NaN | 2020-12-14 | 15 |
628 rows × 6 columns
amount_of_events = events_upd.groupby('days')['user_id'].count().reset_index()
amount_of_events['%'] = round(amount_of_events['user_id'] / amount_of_events['user_id'].sum() * 100, 2)
amount_of_events
| days | user_id | % | |
|---|---|---|---|
| 0 | 0 | 8131 | 32.92 |
| 1 | 1 | 3754 | 15.20 |
| 2 | 2 | 2578 | 10.44 |
| 3 | 3 | 1805 | 7.31 |
| 4 | 4 | 1494 | 6.05 |
| 5 | 5 | 1208 | 4.89 |
| 6 | 6 | 1070 | 4.33 |
| 7 | 7 | 986 | 3.99 |
| 8 | 8 | 762 | 3.09 |
| 9 | 9 | 606 | 2.45 |
| 10 | 10 | 517 | 2.09 |
| 11 | 11 | 347 | 1.40 |
| 12 | 12 | 347 | 1.40 |
| 13 | 13 | 251 | 1.02 |
| 14 | 14 | 214 | 0.87 |
| 15 | 15 | 171 | 0.69 |
| 16 | 16 | 94 | 0.38 |
| 17 | 17 | 86 | 0.35 |
| 18 | 18 | 80 | 0.32 |
| 19 | 19 | 60 | 0.24 |
| 20 | 20 | 69 | 0.28 |
| 21 | 21 | 33 | 0.13 |
| 22 | 22 | 31 | 0.13 |
| 23 | 23 | 4 | 0.02 |
Как мы видим в данных есть 628 событий, совершенных позже 14 дней со дня регистрации. Также мы наблюдаем, что практически 60% всех событий происходят в первые 3 дня с момента регистрации. Учитывая это можем предположить, что горизонт событий в 14 дней не настолько принципиален, как это просится в ТЗ. Отфильтруем даные по этим значениям.
#фильтруем данные по дате регистрации
events_upd = events_upd.query('days <= 14')
#разделяем пользователей в зависимости от группы
participants_upd_a = participants_rec.query('group == "A"')['user_id']
participants_upd_b = participants_rec.query('group == "B"')['user_id']
#разделяем пользователей на группы A и B
events_upd_a = events_upd.query('user_id in @participants_upd_a')
events_upd_b = events_upd.query('user_id in @participants_upd_b')
marketing_events.sort_values(by = 'start_dt')
| name | regions | start_dt | finish_dt | |
|---|---|---|---|---|
| 6 | Chinese New Year Promo | APAC | 2020-01-25 | 2020-02-07 |
| 1 | St. Valentine's Day Giveaway | EU, CIS, APAC, N.America | 2020-02-14 | 2020-02-16 |
| 8 | International Women's Day Promo | EU, CIS, APAC | 2020-03-08 | 2020-03-10 |
| 2 | St. Patric's Day Promo | EU, N.America | 2020-03-17 | 2020-03-19 |
| 3 | Easter Promo | EU, CIS, APAC, N.America | 2020-04-12 | 2020-04-19 |
| 7 | Labor day (May 1st) Ads Campaign | EU, CIS, APAC | 2020-05-01 | 2020-05-03 |
| 9 | Victory Day CIS (May 9th) Event | CIS | 2020-05-09 | 2020-05-11 |
| 11 | Dragon Boat Festival Giveaway | APAC | 2020-06-25 | 2020-07-01 |
| 4 | 4th of July Promo | N.America | 2020-07-04 | 2020-07-11 |
| 13 | Chinese Moon Festival | APAC | 2020-10-01 | 2020-10-07 |
| 12 | Single's Day Gift Promo | APAC | 2020-11-11 | 2020-11-12 |
| 5 | Black Friday Ads Campaign | EU, CIS, APAC, N.America | 2020-11-26 | 2020-12-01 |
| 0 | Christmas&New Year Promo | EU, N.America | 2020-12-25 | 2021-01-03 |
| 10 | CIS New Year Gift Lottery | CIS | 2020-12-30 | 2021-01-07 |
Как мы наблюдаем время проведения теста совпадает с Christmas&New Year Promo. Последние 5 дней теста с 2020-12-25 по 2020-12-30 проводились параллельно с новогодним промо.
proc = events_upd[events_upd['event_dt'] >= '2020-12-25']['event_name'].count() / events_upd['user_id'].count()
display(f'Процент событий во время промо от всех событий: {proc:.1%}')
'Процент событий во время промо от всех событий: 11.8%'
users_eu = users_rec.query('region == "EU"')
table = users_eu.merge(events, on = 'user_id')
table = table.merge(participants_rec, on = 'user_id')
table
| user_id | first_date | region | device | event_dt | event_name | details | group | ab_test | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | D72A72121175D8BE | 2020-12-07 | EU | PC | 2020-12-07 21:52:10 | product_page | NaN | A | recommender_system_test |
| 1 | D72A72121175D8BE | 2020-12-07 | EU | PC | 2020-12-07 21:52:07 | login | NaN | A | recommender_system_test |
| 2 | DD4352CDCF8C3D57 | 2020-12-07 | EU | Android | 2020-12-07 15:32:54 | product_page | NaN | B | recommender_system_test |
| 3 | DD4352CDCF8C3D57 | 2020-12-07 | EU | Android | 2020-12-08 08:29:31 | product_page | NaN | B | recommender_system_test |
| 4 | DD4352CDCF8C3D57 | 2020-12-07 | EU | Android | 2020-12-10 18:18:27 | product_page | NaN | B | recommender_system_test |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 23415 | 0416B34D35C8C8B8 | 2020-12-20 | EU | Android | 2020-12-21 22:28:29 | product_page | NaN | A | recommender_system_test |
| 23416 | 0416B34D35C8C8B8 | 2020-12-20 | EU | Android | 2020-12-24 09:12:51 | product_page | NaN | A | recommender_system_test |
| 23417 | 0416B34D35C8C8B8 | 2020-12-20 | EU | Android | 2020-12-20 20:58:25 | login | NaN | A | recommender_system_test |
| 23418 | 0416B34D35C8C8B8 | 2020-12-20 | EU | Android | 2020-12-21 22:28:29 | login | NaN | A | recommender_system_test |
| 23419 | 0416B34D35C8C8B8 | 2020-12-20 | EU | Android | 2020-12-24 09:12:49 | login | NaN | A | recommender_system_test |
23420 rows × 9 columns
#расчет пользователей, входящих в оба теста
participants_two_tests = participants.groupby('user_id')['ab_test'].count().reset_index()
participants_two_tests = participants_two_tests.query('ab_test > 1')['user_id']
count_of_two_tests = table.query('user_id in @participants_two_tests')['user_id'].nunique()
display(f'Количество пользователей, которые участвовали в обоих тестах: {count_of_two_tests}')
#расчет пользователей, входящих в обе группы
participants_two_grps = participants_rec.groupby('user_id')['group'].count().reset_index()
participants_two_grps = participants_two_grps.query('group > 1')['user_id']
count_of_two_grps = table.query('user_id in @participants_two_grps')['user_id'].nunique()
display(f'Количество пользователей, которые были в обеих группах: {count_of_two_grps}')
'Количество пользователей, которые участвовали в обоих тестах: 887'
'Количество пользователей, которые были в обеих группах: 0'
table.groupby('group')['user_id'].nunique()
group A 2604 B 877 Name: user_id, dtype: int64
Как мы видим в группу А входят 2604 человек, а в группу В 887. Разница достаточно большая и мы можем предположить, что размер выборки В будет недостаточен для получения статистически значимых результатов. Более того в данных есть 887 участников, которые были в обеих группах теста. Это тоже неблагоприятное условие для теста, так как одна группа может исказить результаты другой группы. Оценим в какие группы другого теста попали пользователи.
another_test_grps = table.query('user_id in @participants_two_tests')
another_test_grps.groupby('group')['user_id'].nunique()
group A 665 B 222 Name: user_id, dtype: int64
В группе А 665 человек из 2604 принимали участие в обоих тестах, в группе В принимали участие 222 из 877. Получается, что 25% пользователей из каждой группы принимали участие в двух тестах. Это достаточно большая часть выборки, однако удалять этих пользователей из теста не стоит, так как это еще сильнее уменьшит мощность теста. Учтем, что количество таких людей распределено каждой группе равномерно по 25%.
#медианное количество событий для участников группы А
table.query('group == "A"').groupby('user_id')['event_name'].count().median()
6.0
#медианное количество событий для участников группы В
table.query('group == "B"').groupby('user_id')['event_name'].count().median()
6.0
Как мы наблюдаем, несмотря на большое различие в размерах выборок медианное количество событий для каждого участника одинаково - 6 событий.
#медианное количество событий в день для участников группы А
table.query('group == "A"').groupby('first_date')['event_name'].count().median()
1180.0
#медианное количество событий в день для участников группы В
table.query('group == "B"').groupby('first_date')['event_name'].count().median()
242.0
#таблица событий по дням среди групп
events_grp = table.pivot_table(index = 'first_date', columns = 'group', values = 'event_name', aggfunc = 'count').reset_index()
display(events_grp)
fig = px.bar(events_grp, y = ['B','A'] ,x = 'first_date', title='События по дням среди групп')
fig.update_layout(xaxis_title="Дата",
yaxis_title="События",)
fig.show()
| group | first_date | A | B |
|---|---|---|---|
| 0 | 2020-12-07 | 1038 | 1294 |
| 1 | 2020-12-08 | 639 | 242 |
| 2 | 2020-12-09 | 567 | 511 |
| 3 | 2020-12-10 | 316 | 148 |
| 4 | 2020-12-11 | 535 | 75 |
| 5 | 2020-12-12 | 305 | 284 |
| 6 | 2020-12-13 | 249 | 40 |
| 7 | 2020-12-14 | 2945 | 342 |
| 8 | 2020-12-15 | 1582 | 149 |
| 9 | 2020-12-16 | 1180 | 707 |
| 10 | 2020-12-17 | 1497 | 176 |
| 11 | 2020-12-18 | 1634 | 234 |
| 12 | 2020-12-19 | 1505 | 212 |
| 13 | 2020-12-20 | 1682 | 313 |
| 14 | 2020-12-21 | 2635 | 384 |
Как мы видимо по таблице и графику событий в группе А в разы больше, чем событий в группе В.
test = table.pivot_table(index='event_name', columns='group', values='user_id', aggfunc='nunique').reset_index()
display(test)
fig = go.Figure()
fig.add_trace(go.Funnel(
name = 'A',
y = test['event_name'],
x = test['A'],
textinfo = "value+percent initial"))
fig.add_trace(go.Funnel(
name = 'B',
orientation = "h",
y = test['event_name'],
x = test['B'],
textposition = "inside",
textinfo = "value+percent initial"))
fig.update_layout(title = 'Конверсия событий для групп',xaxis_title="Количество событий",yaxis_title="Действия")
fig.show()
| group | event_name | A | B |
|---|---|---|---|
| 0 | login | 2604 | 877 |
| 1 | product_cart | 782 | 244 |
| 2 | product_page | 1685 | 493 |
| 3 | purchase | 833 | 249 |
Как мы видим конверсия в воронке убывает для тестируемой группы. В изначальной группе А показатели выше по каждому этапу. На 9% выше product_page, на 4% выше purchase и на 2% выше product_cart.
Корректность данных относительно ТЗ.
Время проведения теста 14 дней.
Трехкратное различие в размерах выборок. Группа А больше тестируемой группы.
Время проведения теста в последние 5 дней совпадает с началом события Christmas&New Year Promo.
Среди пользователей есть участники обеих групп(А и В).
Исходя из получившихся воронок группа А показывает конверсию больше, чем группа В.
users_groups = table.groupby('group')['user_id'].nunique().reset_index()
test['users_A'] = users_groups.query('group == "A"')['user_id'].max()
test['users_B'] = users_groups.query('group == "B"')['user_id'].max()
#составляем функцию для проведения z-testа и проверки статистической значимости между группами
def proportion_test(successes, leads):
# пропорция переходов в первой группе:
p1 = successes[0]/leads[0]
# пропорция переходов во второй группе:
p2 = successes[1]/leads[1]
# пропорция успехов в комбинированном датасете:
p_combined = (successes[0] + successes[1]) / (leads[0] + leads[1])
# разница пропорций в датасетах
difference = p1 - p2
z_value = difference / mth.sqrt(p_combined * (1 - p_combined) * (1/leads[0] + 1/leads[1]))
distr = st.norm(0, 1)
p_value = (1 - distr.cdf(abs(z_value))) * 2
print(successes)
print('p-value: ', p_value)
if p_value < alpha:
print('Отвергаем нулевую гипотезу: между долями есть значимая разница\n')
else:
print(
'Не получилось отвергнуть нулевую гипотезу, нет оснований считать доли разными\n')
alpha = 0.05 / 4
print('H0 - доли уникальных посетителей, побывавших на этапе воронки, одинаковы.')
print('H1 - между долями уникальных посетителей, побывавших на этапе воронки, есть значимая разница. \n')
for idx, event in enumerate(test['event_name']):
successes = np.array([test['A'][idx], test['B'][idx]])
leads = np.array([test['users_A'][idx], test['users_B'][idx]])
print(event)
print(' A B')
proportion_test(successes, leads)
H0 - доли уникальных посетителей, побывавших на этапе воронки, одинаковы. H1 - между долями уникальных посетителей, побывавших на этапе воронки, есть значимая разница. login A B [2604 877] p-value: nan Не получилось отвергнуть нулевую гипотезу, нет оснований считать доли разными product_cart A B [782 244] p-value: 0.21469192029582396 Не получилось отвергнуть нулевую гипотезу, нет оснований считать доли разными product_page A B [1685 493] p-value: 6.942739359416805e-06 Отвергаем нулевую гипотезу: между долями есть значимая разница purchase A B [833 249] p-value: 0.04652482738393027 Не получилось отвергнуть нулевую гипотезу, нет оснований считать доли разными
C:\Users\Admin\AppData\Local\Temp\ipykernel_9404\156454465.py:17: RuntimeWarning: invalid value encountered in double_scalars
Было проведено 4 проверки гипотез, где сравнивались группы A и В. Вся эта проверка образовала множественный тест. Нами был установлен уровень значимости - 5%. При таком уровне значимости шанс ошибиться и получить ложнопозитивный реузльтат составляет примерно примерно 18.54%. На основе z-testa мы оставляем все гипотезы, кроме гипотезы на событии purchase. Значит статистически значимое различие между группами все же есть.
Нами была составлена воронка продаж. Был провден A/B-тест с проверкой 4 гиипотез и 2 групп. На основе результатов исследования мы можем сделать вывод, что статистически важное различия между группами все же присутствует, а значит тестирование было проведено при некорректных данных. Стоит увеличить выборки и подвести их примерно к одному размеру. Важно провести тест в свободное от других маркетинговых событий время. Воронка продаж показала, что группа А превосходит группу В, поэтому рекомендую обдумать преобразование тестируемой группы и сделать ее более отличающейся от группы А.